﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using LanguageExt.Traits;
using System.Runtime.CompilerServices;

namespace LanguageExt;

public static partial class Prelude
{
    /// <summary>
    /// Monadic join
    /// </summary>
    [Pure]
    public static Option<A> flatten<A>(Option<Option<A>> ma) =>
        ma.Bind(identity);

    /// <summary>
    /// Subtract the Ts
    /// </summary>
    /// <param name="lhs">Left-hand side of the operation</param>
    /// <param name="rhs">Right-hand side of the operation</param>
    /// <returns>lhs - rhs</returns>
    [Pure]
    public static Option<T> subtract<NUM, T>(Option<T> lhs, Option<T> rhs) where NUM : Num<T> =>
        lhs.Subtract<NUM, T>(rhs);

    /// <summary>
    /// Find the product of the Ts
    /// </summary>
    [Pure]
    public static Option<T> product<NUM, T>(Option<T> lhs, Option<T> rhs) where NUM : Num<T> =>
        lhs.Product<NUM, T>(rhs);

    /// <summary>
    /// Divide the Ts
    /// </summary>
    /// <param name="lhs">Left-hand side of the operation</param>
    /// <param name="rhs">Right-hand side of the operation</param>
    /// <returns>lhs / rhs</returns>
    [Pure]
    public static Option<T> divide<NUM, T>(Option<T> lhs, Option<T> rhs) where NUM : Num<T> =>
        lhs.Divide<NUM, T>(rhs);

    /// <summary>
    /// Add the Ts
    /// </summary>
    /// <param name="lhs">Left-hand side of the operation</param>
    /// <param name="rhs">Right-hand side of the operation</param>
    /// <returns>lhs / rhs</returns>
    [Pure]
    public static Option<T> add<NUM, T>(Option<T> lhs, Option<T> rhs) where NUM : Num<T> =>
        lhs.Add<NUM, T>(rhs);

    /// <summary>
    /// Check if Option is in a Some state
    /// </summary>
    /// <typeparam name="T">T</typeparam>
    /// <param name="value">Option</param>
    /// <returns>True if value is in a Some state</returns>
    [Pure]
    public static bool isSome<T>(Option<T> value) =>
        value.IsSome;

    /// <summary>
    /// Check if Option is in a None state
    /// </summary>
    /// <typeparam name="T">T</typeparam>
    /// <param name="value">Option</param>
    /// <returns>True if value is in a None state</returns>
    [Pure]
    public static bool isNone<T>(Option<T> value) =>
        value.IsNone;

    /// <summary>
    /// 'No value' state of Option T.
    /// </summary>
    public static readonly Fail<Unit> None = new (default);

    /// <summary>
    /// Create a `Some` of `A`
    /// </summary>
    /// <typeparam name="A">Bound value type</typeparam>
    /// <param name="value">Non-null value to be made optional</param>
    /// <returns>`Option〈A〉` in a `Some` state or throws `ValueIsNullException`
    /// if `isnull(value)`.</returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Option<A> Some<A>(A value) =>
        Option.Some(value);

    /// <summary>
    /// Create a `Some` of `A` from a `Nullable〈A〉`
    /// </summary>
    /// <typeparam name="A">Bound value type</typeparam>
    /// <param name="value">Non-null value to be made optional</param>
    /// <returns>`Option〈A〉` in a `Some` state or throws `ValueIsNullException`
    /// if `isnull(value)`</returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Option<A> Some<A>(A? value) where A : struct =>
        value.HasValue
            ? new Option<A>(value.Value)
            : throw new ValueIsNullException();

    /// <summary>
    /// Create an `Option` of `A`
    /// </summary>
    /// <typeparam name="A">Bound value type</typeparam>
    /// <param name="value">Value to be made optional, or `null`</param>
    /// <returns>If the value is `null` it will be `None` else `Some(value)`</returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Option<A> Optional<A>(A? value) =>
        value is null
            ? default
            : new Option<A>(value);

    /// <summary>
    /// Create an `Option` of `A`
    /// </summary>
    /// <typeparam name="A">Bound value type</typeparam>
    /// <param name="value">Value to be made optional, or null</param>
    /// <returns>If the value is `null` it will be `None` else `Some(value)`</returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Option<A> Optional<A>(A? value) where A : struct =>
        value.HasValue
            ? new Option<A>(value.Value)
            : default;

    /// <summary>
    /// Invokes the action if Option is in the Some state, otherwise nothing happens.
    /// </summary>
    /// <param name="f">Action to invoke if Option is in the Some state</param>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Unit ifSome<T>(Option<T> option, Action<T> Some) => 
        option.IfSome(Some);

    /// <summary>
    /// Returns the result of invoking the None() operation if the optional 
    /// is in a None state, otherwise the bound Some(x) value is returned.
    /// </summary>
    /// <remarks>Will not accept a null return value from the None operation</remarks>
    /// <param name="None">Operation to invoke if the structure is in a None state</param>
    /// <returns>Result of invoking the None() operation if the optional 
    /// is in a None state, otherwise the bound Some(x) value is returned.</returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static T ifNone<T>(Option<T> option, Func<T> None) =>
        option.IfNone(None);

    /// <summary>
    /// Returns the noneValue if the optional is in a None state, otherwise
    /// the bound Some(x) value is returned.
    /// </summary>
    /// <remarks>Will not accept a null noneValue</remarks>
    /// <param name="noneValue">Value to return if in a None state</param>
    /// <returns>noneValue if the optional is in a None state, otherwise
    /// the bound Some(x) value is returned</returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static T ifNone<T>(Option<T> option, T noneValue) =>
        option.IfNone(noneValue);

    /// <summary>
    /// Match the two states of the Option and return a non-null R.
    /// </summary>
    /// <typeparam name="B">Return type</typeparam>
    /// <param name="Some">Some match operation. Must not return null.</param>
    /// <param name="None">None match operation. Must not return null.</param>
    /// <returns>A non-null B</returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static R match<T, R>(Option<T> option, Func<T, R> Some, Func<R> None) =>
        option.Match(Some, None);

    /// <summary>
    /// Match the two states of the Option
    /// </summary>
    /// <param name="Some">Some match operation</param>
    /// <param name="None">None match operation</param>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Unit match<T>(Option<T> option, Action<T> Some, Action None) =>
        option.Match(Some, None);

    /// <summary>
    /// <para>
    /// Option types are like lists of 0 or 1 items, and therefore follow the 
    /// same rules when folding.
    /// </para><para>
    /// In the case of lists, 'Fold', when applied to a binary
    /// operator, a starting value(typically the left-identity of the operator),
    /// and a list, reduces the list using the binary operator, from left to
    /// right:
    /// </para><para>
    /// Note that, since the head of the resulting expression is produced by
    /// an application of the operator to the first element of the list,
    /// 'Fold' can produce a terminating expression from an infinite list.
    /// </para>
    /// </summary>
    /// <typeparam name="S">Aggregate state type</typeparam>
    /// <param name="state">Initial state</param>
    /// <param name="Some">Folder function, applied if Option is in a Some state</param>
    /// <param name="None">Folder function, applied if Option is in a None state</param>
    /// <returns>The aggregate state</returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static S bifold<S, A>(Option<A> option, S state, Func<S, A, S> Some, Func<S, S> None) =>
        option.BiFold(state, Some, None);

    /// <summary>
    /// <para>
    /// Option types are like lists of 0 or 1 items, and therefore follow the 
    /// same rules when folding.
    /// </para><para>
    /// In the case of lists, 'Fold', when applied to a binary
    /// operator, a starting value(typically the left-identity of the operator),
    /// and a list, reduces the list using the binary operator, from left to
    /// right:
    /// </para><para>
    /// Note that, since the head of the resulting expression is produced by
    /// an application of the operator to the first element of the list,
    /// 'Fold' can produce a terminating expression from an infinite list.
    /// </para>
    /// </summary>
    /// <typeparam name="S">Aggregate state type</typeparam>
    /// <param name="state">Initial state</param>
    /// <param name="Some">Folder function, applied if Option is in a Some state</param>
    /// <param name="None">Folder function, applied if Option is in a None state</param>
    /// <returns>The aggregate state</returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static S bifold<S, A>(Option<A> option, S state, Func<S, A, S> Some, Func<S, Unit, S> None) =>
        option.BiFold(state, Some, None);

    /// <summary>
    /// Apply a predicate to the bound value.  If the Option is in a None state
    /// then True is returned (because the predicate applies for-all values).
    /// If the Option is in a Some state the value is the result of running 
    /// applying the bound value to the predicate supplied.        
    /// </summary>
    /// <param name="pred">Predicate to apply</param>
    /// <returns>If the Option is in a None state then True is returned (because 
    /// the predicate applies for-all values).  If the Option is in a Some state
    /// the value is the result of running applying the bound value to the 
    /// predicate supplied.</returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool forall<A>(Option<A> option, Func<A, bool> pred) =>
        option.ForAll(pred);

    /// <summary>
    /// Apply a predicate to the bound value.  If the Option is in a None state
    /// then True is returned if invoking None returns True.
    /// If the Option is in a Some state the value is the result of running 
    /// applying the bound value to the Some predicate supplied.        
    /// </summary>
    /// <param name="Some">Predicate to apply if in a Some state</param>
    /// <param name="None">Predicate to apply if in a None state</param>
    /// <returns>If the Option is in a None state then True is returned if 
    /// invoking None returns True. If the Option is in a Some state the value 
    /// is the result of running applying the bound value to the Some predicate 
    /// supplied.</returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool biforall<A>(Option<A> option, Func<A, bool> Some, Func<Unit, bool> None) =>
        option.BiForAll(Some, None);

    /// <summary>
    /// Apply a predicate to the bound value.  If the Option is in a None state
    /// then True is returned if invoking None returns True.
    /// If the Option is in a Some state the value is the result of running 
    /// applying the bound value to the Some predicate supplied.        
    /// </summary>
    /// <param name="Some">Predicate to apply if in a Some state</param>
    /// <param name="None">Predicate to apply if in a None state</param>
    /// <returns>If the Option is in a None state then True is returned if 
    /// invoking None returns True. If the Option is in a Some state the value 
    /// is the result of running applying the bound value to the Some predicate 
    /// supplied.</returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool biforall<A>(Option<A> option, Func<A, bool> Some, Func<bool> None) =>
        option.BiForAll(Some, None);

    /// <summary>
    /// <para>
    /// Return the number of bound values in this structure:
    /// </para>
    /// <para>
    ///     None = 0
    /// </para>
    /// <para>
    ///     Some = 1
    /// </para>
    /// </summary>
    /// <returns></returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static int count<A>(Option<A> option) =>
        option.Count();

    /// <summary>
    /// Apply a predicate to the bound value.  If the Option is in a None state
    /// then True is returned if invoking None returns True.
    /// If the Option is in a Some state the value is the result of running 
    /// applying the bound value to the Some predicate supplied.        
    /// </summary>
    /// <param name="pred">Predicate to apply</param>
    /// <returns>If the Option is in a None state then True is returned if 
    /// invoking None returns True. If the Option is in a Some state the value 
    /// is the result of running applying the bound value to the Some predicate 
    /// supplied.</returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool exists<A>(Option<A> option, Func<A, bool> pred) =>
        option.Exists(pred);

    /// <summary>
    /// Apply a predicate to the bound value.  If the Option is in a None state
    /// then True is returned if invoking None returns True.
    /// If the Option is in a Some state the value is the result of running 
    /// applying the bound value to the Some predicate supplied.        
    /// </summary>
    /// <param name="Some">Predicate to apply if in a Some state</param>
    /// <param name="None">Predicate to apply if in a None state</param>
    /// <returns>If the Option is in a None state then True is returned if 
    /// invoking None returns True. If the Option is in a Some state the value 
    /// is the result of running applying the bound value to the Some predicate 
    /// supplied.</returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool biexists<A>(Option<A> option, Func<A, bool> Some, Func<Unit, bool> None) =>
        option.BiExists(Some, None);

    /// <summary>
    /// Apply a predicate to the bound value.  If the Option is in a None state
    /// then True is returned if invoking None returns True.
    /// If the Option is in a Some state the value is the result of running 
    /// applying the bound value to the Some predicate supplied.        
    /// </summary>
    /// <param name="Some">Predicate to apply if in a Some state</param>
    /// <param name="None">Predicate to apply if in a None state</param>
    /// <returns>If the Option is in a None state then True is returned if 
    /// invoking None returns True. If the Option is in a Some state the value 
    /// is the result of running applying the bound value to the Some predicate 
    /// supplied.</returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool biexists<A>(Option<A> option, Func<A, bool> Some, Func<bool> None) =>
        option.BiExists(Some, None);

    /// <summary>
    /// Projection from one value to another 
    /// </summary>
    /// <typeparam name="B">Resulting functor value type</typeparam>
    /// <param name="f">Projection function</param>
    /// <returns>Mapped functor</returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Option<B> map<A, B>(Option<A> option, Func<A, B> f) =>
        option.Map(f);

    /// <summary>
    /// Projection from one value to another
    /// </summary>
    /// <typeparam name="B">Resulting functor value type</typeparam>
    /// <param name="Some">Projection function</param>
    /// <param name="None">Projection function</param>
    /// <returns>Mapped functor</returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Option<B> bimap<A, B>(Option<A> option, Func<A, B> Some, Func<B> None) =>
        option.BiMap(Some, None);

    /// <summary>
    /// Projection from one value to another
    /// </summary>
    /// <typeparam name="B">Resulting functor value type</typeparam>
    /// <param name="Some">Projection function</param>
    /// <param name="None">Projection function</param>
    /// <returns>Mapped functor</returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Option<B> bimap<A, B>(Option<A> option, Func<A, B> Some, Func<Unit, B> None) =>
        option.BiMap(Some, None);

    /// <summary>
    /// Apply a predicate to the bound value (if in a Some state)
    /// </summary>
    /// <param name="pred">Predicate to apply</param>
    /// <returns>Some(x) if the Option is in a Some state and the predicate
    /// returns True.  None otherwise.</returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Option<T> filter<T>(Option<T> option, Func<T, bool> pred) =>
        option.Filter(pred);

    /// <summary>
    /// Monadic bind operation
    /// </summary>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Option<R> bind<T, R>(Option<T> option, Func<T, Option<R>> binder) =>
        option.Bind(binder);

    /// <summary>
    /// Match the two states of the list of Options
    /// </summary>
    /// <param name="Some">Some match operation</param>
    /// <param name="None">None match operation</param>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static IEnumerable<R> match<T, R>(
        IEnumerable<Option<T>> list,
        Func<T, IEnumerable<R>> Some,
        Func<IEnumerable<R>> None) =>
        list.Match(
            None,
            opt     => match(opt, Some, None),
            (x, xs) => match(x,   Some, None).ConcatFast(match(xs, Some, None)));

    /// <summary>
    /// Match the two states of the list of Options
    /// </summary>
    /// <param name="Some">Some match operation</param>
    /// <param name="None">None match operation</param>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static IEnumerable<R> match<T, R>(IEnumerable<Option<T>> list,
                                             Func<T, IEnumerable<R>> Some,
                                             IEnumerable<R> None) =>
        match(list, Some, () => None);

    /// <summary>
    /// Extracts from a list of 'Option' all the 'Some' elements.
    /// All the 'Some' elements are extracted in order.
    /// </summary>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static IEnumerable<T> somes<T>(IEnumerable<Option<T>> list) =>
        list.Somes();

    /// <summary>
    /// Convert the Option to an immutable list of zero or one items
    /// </summary>
    /// <returns>An immutable list of zero or one items</returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Lst<T> toList<T>(Option<T> option) =>
        option.ToList();

    /// <summary>
    /// Convert the Option to an enumerable of zero or one items
    /// </summary>
    /// <returns>An enumerable of zero or one items</returns>
    [Pure]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Arr<T> toArray<T>(Option<T> option) =>
        option.ToArray();
}
